package jannex.internal;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SuppressWarnings("unchecked")
public final class ReflectionUtils {

    private ReflectionUtils() {
    }

    public static final Map<Class<?>, Object> DEFAULT_TYPES = new HashMap<>();
    static {
        DEFAULT_TYPES.put(boolean.class, false);
        DEFAULT_TYPES.put(char.class, '\u0000');
        DEFAULT_TYPES.put(byte.class, (byte) 0);
        DEFAULT_TYPES.put(short.class, (short) 0);
        DEFAULT_TYPES.put(int.class, 0);
        DEFAULT_TYPES.put(long.class, 0l);
        DEFAULT_TYPES.put(float.class, 0f);
        DEFAULT_TYPES.put(double.class, 0d);
    }

    public static final Map<Class<?>, Class<?>> WRAPPER_TYPES = new HashMap<>();
    static {
        WRAPPER_TYPES.put(boolean.class, Boolean.class);
        WRAPPER_TYPES.put(char.class, Character.class);
        WRAPPER_TYPES.put(byte.class, Byte.class);
        WRAPPER_TYPES.put(short.class, Short.class);
        WRAPPER_TYPES.put(int.class, Integer.class);
        WRAPPER_TYPES.put(long.class, Long.class);
        WRAPPER_TYPES.put(float.class, Float.class);
        WRAPPER_TYPES.put(double.class, Double.class);
    }

    public static final List<Class<?>> PRIMITIVISH_TYPES = Arrays.<Class<?>>asList(
            boolean.class, Boolean.class,
            char.class, Character.class,
            byte.class, Byte.class,
            short.class, Short.class,
            int.class, Integer.class,
            long.class, Long.class,
            float.class, Float.class,
            double.class, Double.class,
            String.class);

    public static <T> T getDefault(Class<T> cls) {
        if (DEFAULT_TYPES.containsKey(cls)) {
            return cls.cast(DEFAULT_TYPES.get(cls));
        }
        return null;
    }

    public static boolean isPrimitivish(Class<?> cls) {
        return PRIMITIVISH_TYPES.contains(cls);
    }

    public static <T> Class<T> getClass(T t) {
        return (Class<T>) t.getClass();
    }


    public static <T> T cast(Object o) {
        return (T) o;
    }

    public static long castLong(Object l) {
        return l == null ? 0l : (long) l;
    }

    public static int castInt(Object i) {
        return i == null ? 0 : (int) i;
    }

    public static short castShort(Object s) {
        return s == null ? 0 : (short) s;
    }

    public static byte castByte(Object b) {
        return b == null ? (byte) 0 : (byte) b;
    }

    public static boolean castBoolean(Object b) {
        return b != null && (boolean) b;
    }

    public static char castChar(Object c) {
        return c == null ? '\u0000' : (char) c;
    }

    public static double castDouble(Object d) {
        return d == null ? 0d : (double) d;
    }

    public static float castFloat(Object f) {
        return f == null ? 0f : (float) f;
    }

    public static byte[] castByteArray(Object array) {
        if (array == null) {
            return null;
        }
        if (array.getClass().getComponentType().equals(Byte.TYPE)) {
            return (byte[]) array;
        } else {
            Object[] rawArray = (Object[]) array;
            byte[] ts = new byte[rawArray.length];
            for (int i = 0; i < rawArray.length; i++) {
                ts[i] = castByte(rawArray[i]);
            }
            return ts;
        }
    }

    /**
     * Can cast primitive arrays to object arrays (not vice versa), use corresponding method.
     */
    public static <T> T[] castArray(Class<T> arrayClass, Object array) {
        if (array == null) {
            return null;
        }
        if (arrayClass.isPrimitive()) {
            throw new UnsupportedOperationException("Cannot return array of primitive as array of objects");
        }

        Class<?> componentType = array.getClass().getComponentType();

        if (componentType.equals(arrayClass)) {
            return (T[]) array;
        } else {
            T[] ts = (T[]) Array.newInstance(arrayClass, Array.getLength(array));
            for (int i = 0; i < ts.length; i++) {
                ts[i] = arrayClass.cast(Array.get(array, i));
            }
            return ts;
        }
    }

    /**
     * Can cast primitive arrays to object arrays (not vice versa), use corresponding method.
     */
    public static <T> T[] castArray(Object array) {
        Class<?> componentType = array.getClass().getComponentType();
        if (!componentType.isPrimitive()) {
            return (T[]) castArray(componentType, array);
        }
        return (T[]) castArray(WRAPPER_TYPES.get(componentType), array);
    }

}
